PATHMac OS 8 Developer Documentation > Operating System Services > Multiprocessing Services >

Adding Multitasking Capability to Applications Using Multiprocessing Services

   

Creating Tasks

After determining how many processors are available, you can go ahead and create tasks for your application.

Each task must be a function that takes one 32-bit parameter and returns a 32-bit result of type OSStatus when it finishes. The 32-bit input parameter can be any information that the task needs to perform its function. Some examples of input are

You create a task by calling the function MPCreateTask . The code in Listing 3-1 shows how you can create a number of identical tasks. Identical tasks can be useful when you want to divide up a large calculation (such as a image filtering operation) among several processors to improve performance.

Listing 3-1   Creating tasks

#define kMPStackSize 0 // use default stack size
#define kMPTaskOptions 0 // use no options

typedef struct {
    long firstThing;
    long totalThings;
    } sWorkParams, *sWorkParamsPtr;

typedef struct {
    MPTaskID taskID;
    MPQueueID requestQueue;
    MPQueueID resultQueue;
    sWorkParams params;
    } sTaskData, *sTaskDataPtr;

sTaskDataPtr myTaskData;
UInt32 numProcessors;
MPQueueID notificationQueue;

void CreateMPTasks( void ) {

    OSErr theErr;
    UInt32 i;

    theErr = noErr;

    /* Assume single processor mode */
    numProcessors = 1;

    /* Initialize remaining globals */
    myTaskData = NULL;
    notificationQueue = NULL;

    /* If the library is present then create the tasks */
    if( MPLibraryIsLoaded() ) {
        numProcessors = MPProcessorsScheduled();
        myTaskData = (sTaskDataPtr)NewPtrClear
                ( numProcessors * sizeof( sTaskData ) );
        theErr = MemError();
        if( theErr == noErr )
                theErr = MPCreateQueue( &notificationQueue );
        for( i = 0; i < numProcessors && theErr == noErr; i++ ) {
            if( theErr == noErr )
                    theErr = MPCreateQueue(&myTaskData[i].requestQueue );
            if( theErr == noErr )
                    theErr = MPCreateQueue(&myTaskData[i].resultQueue );
            if( theErr == noErr )
                    theErr = MPCreateTask( MyTask, &myTaskData[i],
                        kMPStackSize, notificationQueue,
                        NULL, NULL, kMPTaskOptions,
                        &myTaskData[i].taskID );
            }
        }

/* If something went wrong, just go back to single processor mode */
    if( theErr != noErr ) {
        StopMPTasks();
        numProcessors = 1;
        }
    }

The sTaskData structure defines a number of values to be used with the task, such as the task ID, the IDs of the message queues used with the task, and a pointer to parameters to pass to the task. A pointer to a structure of this type is passed in the function MPCreateTask .

The variable notificationQueueID holds the ID of the notification queue to associate with the tasks. When a task terminates, it sends a message to this queue. After sending a termination request, the application typically polls this queue to determine when the task has actually terminated.

The CreateMPTasks function creates as many identical tasks as there are available processors (as stored in numProcessors ). If for some reason the tasks cannot be created (for example, if Multiprocessing Services is not available), the variable numProcessors is set to 1 and the application should do the work of the tasks itself without making any Multiprocessing Services calls.

Before creating the tasks, CreateMPTasks calls the function MPCreateQueue to create a notification queue to be used by all the tasks. It then calls the Memory Manager function NewPtrClear to allocate memory for all the myTaskData structures required (in the case of this example, one per task).

Next, CreateMPTasks iterates over the number of requested tasks. For each iteration, it does the following:

Each task is assigned its own unique ID, which is passed back in the taskID field of the myTaskData task structure.

Although not a requirement, you can assign a relative weight to each task by calling the function MPSetTaskWeight . The task weight is a value that indicates the amount of processor attention to give this task relative to all other eligible tasks. If, as in this example, you create a number of identical tasks, each would probably be given equal weight.

The sample task in Listing 3-2 calls one of two different functions depending on the request is placed on its queue.

Listing 3-2   A sample task

#define kMyRequestOne 1
#define kMyRequestTwo 2

#define kMyResultException -1

OSStatus MyTask( void *parameter ) {

    OSErr theErr;
    sTaskDataPtr p;
    Boolean finished;
    UInt32 message;

    theErr = noErr;

    /* Get a pointer to this task's unique data */
    p = (sTaskDataPtr)parameter ;

    /* Process each request handed to the task and return a result */
    finished = false;
    while( !finished ) {
        theErr = MPWaitOnQueue( p->requestQueue, (void **)&message,
                NULL, NULL, kDurationForever );
        if( theErr == noErr ) {
/* Pick a function to call and pass in the parameters. */
/* The parameters should be set up prior to sending the */
/* message just received. Note that we could also just */
/* pass in a pointer to the desired function instead of */
/* using a selector. */
            switch( message ) {
                case kMyRequestOne:
                    theErr = fMyTaskFunctionOne( &p->params );
                    break;
                case kMyRequestTwo:
                    theErr = fMyTaskFunctionTwo( &p->params );
                    break;
                default:
                    finished = true;
                    theErr = kMyResultException;
                }
            MPNotifyQueue( p->resultQueue, (void *)theErr, NULL, NULL );
            }
        else
            finished = true;
        }

    /* Task is finished now */
    return( theErr );
    }

This task takes one parameter, a pointer to its task data structure. This structure contains all the information that is needed for the life of the task, such as the request and result queues created for it, and any input necessary when processing a task request. The input parameters are passed along to the requested function.

After some initialization, the task sets the finished flag to false and then spends the rest of its time in a while loop processing message requests. The task calls the function MPWaitOnQueue , which waits indefinitely until a message appears on its request queue. In this case, the message indicates which function the task is to call. When a message is received, MyTask checks the request message to determine which function is desired and calls through to that function. Upon return, it posts a message on the result queue by calling MPNotifyQueue and then calls MPWaitOnQueue again to wait for the next message.

Note that if you are creating tasks on-the-fly, you may want to have your task dispose of its task record (pointed to by p ) upon completion of the task. For more information about allocating and disposing of memory in tasks, see Allocating Memory in Tasks .


© 1999 Apple Computer, Inc. – (Last Updated 07 May 99)